using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Dapper;

namespace Simplicity.Data.SQL
{
    public abstract class SqlExpression<T> where T : class
    {

        #region Private Properties

        public string SqlStatement { get; set; }
        public DynamicParameters Parameters { get; set; }

        #endregion


        internal void ParseExpressionTree(Expression<Func<T, bool>> ExpressionTree)
        {
            SqlStatement = (string)ParseExpression(ExpressionTree.Body);
        }


        internal void ParseExpressionTree(Expression<Func<T, object>> ExpressionTree)
        {

            SqlStatement = (string)ParseExpression(ExpressionTree.Body);
        }


        #region Private Methods

        #region Parse Expression
        private object ParseExpression(Expression Expression, bool Evaluate = false)
        {
            if (Expression is BinaryExpression)
                return ParseBinaryExpression(Expression);
            else if (Expression is ParameterExpression)
                return ParseParameterExpression(Expression);
            else if (Expression is MemberExpression)
                return ParseMemberExpression(Expression, Evaluate);
            else if (Expression is ConstantExpression)
                return ParseConstantExpression(Expression);
            else if (Expression is NewExpression)
                return ParseNewExpression(Expression);
            else if (Expression is UnaryExpression)
                return ParseUnaryExpression(Expression);
            else return string.Empty;
        }

        private object ParseUnaryExpression(Expression Expression)
        {
            UnaryExpression expr = (UnaryExpression)Expression;
            if (expr.NodeType == ExpressionType.Convert)
                return ParseExpression(expr.Operand);
            return string.Empty;
        }

        private object ParseBinaryExpression(Expression Expression)
        {
            BinaryExpression expr = (BinaryExpression)Expression;
            var l = ParseExpression(expr.Left);
            var r = ParseExpression(expr.Right, true);
            return GetFormat(l, expr.NodeType, r);
        }

        private object ParseParameterExpression(Expression Expression)
        {
            return string.Empty;
        }

        private object ParseMemberExpression(Expression Expression, bool Evaluate)
        {
            if (Evaluate)
                return GetValue((MemberExpression)Expression);
            return GetName((MemberExpression)Expression);
        }

        private object ParseConstantExpression(Expression Expression)
        {
            return ((ConstantExpression)Expression).Value;
        }

        private object ParseNewExpression(Expression Expression)
        {
            NewExpression expr = (NewExpression)Expression;
            string result = string.Empty;
            for (var i = 0; i < expr.Members.Count; i++)
            {
                string field = string.Format("[{0}]",
                    expr.Members[i].Name);
                var arg = expr.Arguments[i];
                var argName = (string)ParseExpression(arg);
                if (!(field == argName))
                {
                    field = string.Format("{0} AS {1}", argName, field);
                }
                result = (result.Length > 0) ? string.Join(",", result, field) : field;
            }
            return result;
        }

        #endregion

        #region Helpers
        private string GetName(MemberExpression member)
        {
            return member.Member.Name;
        }

        private string GetFormat(object Left,
            ExpressionType Operator, object Right)
        {
            switch (Operator)
            {
                case ExpressionType.AndAlso:
                    return string.Format("{0} AND {1}",
                        Left, Right);
                case ExpressionType.OrElse:
                    return string.Format("{0} OR {1}",
                        Left, Right);
                case ExpressionType.GreaterThan:
                    Parameters.Add((string)Left, Right);
                    return string.Format("([{0}] > @{0})", Left);
                case ExpressionType.GreaterThanOrEqual:
                    Parameters.Add((string)Left, Right);
                    return string.Format("([{0}] >= @{0})", Left);
                case ExpressionType.LessThan:
                    Parameters.Add((string)Left, Right);
                    return string.Format("([{0}] < @{0})", Left);
                case ExpressionType.LessThanOrEqual:
                    Parameters.Add((string)Left, Right);
                    return string.Format("([{0}] <= @{0})", Left);
                case ExpressionType.NotEqual:
                    Parameters.Add((string)Left, Right);
                    return string.Format("NOT ([{0}] {1} @{0})", Left,
                        Right.GetType().Equals(typeof(Null)) ? "IS" : "=");
                default:
                    Parameters.Add((string)Left, Right);
                    return string.Format("([{0}] {1} @{0})", Left,
                        Right.GetType().Equals(typeof(Null)) ? "IS" : "=");
            }
        }

        private object GetValue(MemberExpression member)
        {
            var memberObj = Expression.Convert(member, typeof(object));
            var lambdaExp = Expression.Lambda<Func<object>>(memberObj);
            var val = lambdaExp.Compile();
            return val();
        }

        #endregion

        #endregion

    }
}

